package pzc.jdbc;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 名前付きパラメータ対応PreparedStatement.
 * <p>
 * 「{aaaCd}」、「{ymd}」等の名前タグに対応したPreparedStatementクラス<br>
 * ・SQL文中では、パラメータマーカー[?]のかわりに、{}で囲んだパラメータ名を記述します。<br>
 * ・パラメータは、インデックスの代わりに名前({}は付けない)を指定して設定します。<br>
 * ・一つのSQL中で同じパラメータを何度でも使用できます。<br>
 * ・SQL中にないパラメータ名を指定しても、パラメータの設定はエラーにならず、無視されます。<br>
 * 　(条件によってSQLを動的に編集する場合でも、パラメータはそれを意識せず全て設定して構いません)<br>
 * ・SQLの実行ログはDEBUGログレベルで出力されますが、特定のSQLだけログ出力したい場合などは、setLogLevelDebug()によりレベルを変更できます。
 * <p><pre>
 * 使用例
 *   String sql = "SELECT description "
 *              + "  FROM mysql.help_topic "
 *              + " WHERE name LIKE {PREFIX}||'%' "
 *              + "   AND help_category_id = {CTG} ";
 *   NamedPreparedStatement pstmt
 *       = new NamedPreparedStatement(con, sql,
 *                                       NamedPreparedStatement.SQL_LOG_MODE_EXECUTABLE);
 *   pstmt.setStringAtName("PREFIX", "R");
 *   pstmt.setIntAtName("CTG", 33);
 *   ResultSet rs = pstmt.executeQuery();
 * </pre>
 *
 */
public class NamedPreparedStatement {
	/** ロガー. */
	private static final Logger logger = Logger.getLogger(NamedPreparedStatement.class.getName());

	/** SQLログモード：ログ出力なし. */
	public static final int SQL_LOG_MODE_NONE = 0;
	/** SQLログモード：ソースSQLイメージ. */
	public static final int SQL_LOG_MODE_SOURCE = 1;
	/** SQLログモード：実行可能SQLイメージ. */
	public static final int SQL_LOG_MODE_EXECUTABLE = 2;
	/** SQLログモード：PreparedStatement生成用SQLイメージ. */
	public static final int SQL_LOG_MODE_PREPARED = 3;
	/** SQLログモード・デフォルト：ログ出力なし. */
//	public static final int SQL_LOG_MODE_DEFAULT = ResourceUtility.getResourceInt("sql.log_mode.default", SQL_LOG_MODE_NONE);
	public static final int SQL_LOG_MODE_DEFAULT = SQL_LOG_MODE_EXECUTABLE;
	/** SQLログモード. */
	private int sqlLogMode;
	/** コネクション. */
	private Connection con;
	/** 出力ログタイトル・デフォルト：SQL. */
	private static final String LOG_TITLE_DEFAULT = "SQL";
	/** 出力ログタイトル. */
	private String logTitle;
	/** デフォルトDEBUGログレベル. */
//	private static final int LOG_LEVEL_DEBUG_DEFAULT = Logger.LV_DEBUG;
	private static final Level LOG_LEVEL_DEBUG_DEFAULT = Level.INFO;
	/** デフォルトINFOログレベル. */
//	private static final int LOG_LEVEL_INFO_DEFAULT = Logger.LV_INFO;
	private static final Level LOG_LEVEL_INFO_DEFAULT = Level.INFO;
	/** デフォルトWARNログレベル. */
//	private static final int LOG_LEVEL_WARN_DEFAULT = Logger.LV_WARN;
	private static final Level LOG_LEVEL_WARN_DEFAULT = Level.WARNING;
	/** デフォルトERRORログレベル. */
//	private static final int LOG_LEVEL_ERROR_DEFAULT = Logger.LV_ERROR;
	private static final Level LOG_LEVEL_ERROR_DEFAULT = Level.SEVERE;
	/** DEBUGログレベル. */
//	private int logLevelDebug = LOG_LEVEL_DEBUG_DEFAULT;
	private Level logLevelDebug = LOG_LEVEL_DEBUG_DEFAULT;
	/** INFOログレベル. */
//	private int logLevelInfo = LOG_LEVEL_INFO_DEFAULT;
	private Level logLevelInfo = LOG_LEVEL_INFO_DEFAULT;
	/** WARNログレベル. */
//	private int logLevelWarn = LOG_LEVEL_WARN_DEFAULT;
	private Level logLevelWarn = LOG_LEVEL_WARN_DEFAULT;
	/** ERRORログレベル. */
//	private int logLevelError = LOG_LEVEL_ERROR_DEFAULT;
	private Level logLevelError = LOG_LEVEL_ERROR_DEFAULT;
	/**
	 * パラメータマーカーMap. <br>
	 * key：パラメータ名<br>
	 * value:パラメータマーカ番号Set
	 */
	private Map<String, Set<Integer>> paramMarkerMap = new TreeMap<String, Set<Integer>>();

	/**
	 * パラメータ値Map. <br>
	 * key：パラメータ名<br>
	 * value:パラメータ文字列(文字パラメータは''付き)
	 */
	private Map<String, String> paramStrMap = new TreeMap<String, String>();

	/** PreparedStatement. */
	private PreparedStatement pstmt;

	/** PreparedStatement生成用SQL. */
	private String sql;
	/** ソースSQL. */
	private String srcSql;

    /** 改行. */
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

	/**
	 * コンストラクタ.
	 * <p>
	 *
	 * @param con
	 *            コネクション
	 * @param srcSql
	 *            名前タグ（{xxCd}、{1}等）付きSQL
	 * @throws SQLException
	 *             SQL例外
	 */
	public NamedPreparedStatement(Connection con, String srcSql) throws SQLException {
		this(con, srcSql, SQL_LOG_MODE_DEFAULT, LOG_TITLE_DEFAULT);
	}

	/**
	 * コンストラクタ.
	 * <p>
	 *
	 * @param con
	 *            コネクション
	 * @param srcSql
	 *            名前タグ（{xxCd}、{1}等）付きSQL
	 * @param sqlLogMode
	 *            SQLログモード
	 * @throws SQLException
	 *             SQL例外
	 */
	public NamedPreparedStatement(Connection con, String srcSql, int sqlLogMode) throws SQLException {
		this(con, srcSql, sqlLogMode, LOG_TITLE_DEFAULT);
	}

	/**
	 * コンストラクタ.
	 * <p>
	 *
	 * @param con
	 *            コネクション
	 * @param srcSql
	 *            名前タグ（{xxCd}、{1}等）付きSQL
	 * @param sqlLogMode
	 *            SQLログモード
	 * @param logTitle
	 *            ログタイトル
	 * @throws SQLException
	 *             SQL例外
	 */
	public NamedPreparedStatement(Connection con, String srcSql, int sqlLogMode, String logTitle) throws SQLException {
		// パラメータの待避
		this.con = con;
		this.srcSql = srcSql;
		this.sqlLogMode = sqlLogMode;
		setLogTitle(logTitle);

		// SQLパラメータの解析
		if (srcSql.indexOf("?") >= 0) {
			logger.log(logLevelError, this.logTitle + "SQL文中にパラメータマーカ[?]があります。");
			logger.log(logLevelError, this.logTitle + srcSql);
		}

		int markerNo = 0;
		StringBuffer sb = new StringBuffer();
		String[] sqlParts = srcSql.split("(?=\\{)|\\}");
		for (int i = 0; i < sqlParts.length; ++i) {
			if (sqlParts[i].startsWith("{")) {
				String markerName = sqlParts[i].substring(1);
				Set<Integer> markers = paramMarkerMap.get(markerName);
				if (markers == null) {
					markers = new TreeSet<Integer>();
					paramMarkerMap.put(markerName, markers);
					paramStrMap.put(markerName, null);
				}
				markers.add(Integer.valueOf(++markerNo));
				sb.append('?');
			} else {
				sb.append(sqlParts[i]);
			}
		}
		this.sql = sb.toString();

		// PreparedStatementの作成
		pstmt = this.con.prepareStatement(this.sql);
	}

	/**
	 * パラメータチェック.
	 * <p>
	 * パラメータの設定漏れをチェックする。
	 *
	 * @return true:ok false:ng
	 */
	private boolean checkParam() {
		boolean ret = true;
		Iterator<Map.Entry<String, String>> ite = paramStrMap.entrySet().iterator();
		while (ite.hasNext()) {
			Entry<String, String> entry = ite.next();
			if (entry.getValue() == null) {
				String logMessage = logTitle + "未設定のパラメータ{" + entry.getKey() + "}";
				logger.log(logLevelWarn, logMessage);
				ret = false;
			}
		}
		return ret;
	}

	/**
	 * ステートメントのクローズ.
	 * <p>
	 * PreparedStatementがOPENしている場合CLOSEする
	 *
	 * @throws SQLException
	 *             SQL例外
	 */
	public void close() throws SQLException {
		if (pstmt != null) {
			pstmt.close();
		}
	}

	/**
	 * SQLの実行（照会）.
	 * <p>
	 * SQLの実行（照会）
	 *
	 * @return 照会結果（ResultSet)
	 * @throws SQLException
	 *             SQL例外
	 */
	public ResultSet executeQuery() throws SQLException {
		ResultSet rs = null;
		checkParam();
		long time = 0;
		try {
			long startTime = System.currentTimeMillis();

			// 実行
			rs = pstmt.executeQuery();

			long endTime = System.currentTimeMillis();
			time = endTime - startTime;
		} catch (SQLException e) {
			logger.log(logLevelWarn, "(SQL例外)"+logTitle+" [ErrorCode:" + e.getErrorCode()
					               + "] [SQLState:" + e.getSQLState() + "] [Message:" + e.getMessage() + "]");
			logger.log(logLevelWarn, getSourceSql());
			logParameter(logLevelWarn);
			throw e;
		} finally {
			logSql(time);
		}
		return rs;
	}

	/**
	 * SQLの実行（更新）.
	 * <p>
	 * SQLの実行（更新）
	 *
	 * @return 更新数
	 * @throws SQLException
	 *             SQL例外
	 */
	public int executeUpdate() throws SQLException {
		int ret = 0;
		checkParam();
		long time = 0;
		try {
			long startTime = System.currentTimeMillis();

			// 実行
			ret = pstmt.executeUpdate();

			long endTime = System.currentTimeMillis();
			time = endTime - startTime;
		} catch (SQLException e) {
			logger.log(logLevelWarn, "(SQL例外)"+logTitle+" [ErrorCode:" + e.getErrorCode()
					               + "] [SQLState:" + e.getSQLState() + "] [Message:" + e.getMessage() + "]");
			logger.log(logLevelWarn, getSourceSql());
			logParameter(logLevelWarn);
			throw e;
		} finally {
			logSql(time);
		}
		return ret;

	}

	/**
	 * パラメータを展開した実行可能な形式のSQLを返す.
	 * <p>
	 * setObjectAtNameでセットしたパラメータは、' 'で囲んだ文字形式で展開するので、修正が必要な場合があります。
	 *
	 * @return sql文字列
	 */
	public String getExecutableSql() {
		StringBuffer sb = new StringBuffer(srcSql.replaceAll(LINE_SEPARATOR, " "));
		Iterator<Map.Entry<String, String>> ite = paramStrMap.entrySet().iterator();
		while (ite.hasNext()) {
			Entry<String, String> entry = ite.next();
			String paramTag = "{" + entry.getKey() + "}";
			int repIndex;
			while ((repIndex = sb.lastIndexOf(paramTag)) >= 0) {
				sb.replace(repIndex, repIndex + paramTag.length(), (String) entry.getValue() == null ? "<<null>>" : entry.getValue());
			}
		}
		return sb.toString();
	}

	/**
	 * @return ERRORログレベル
	 */
	public Level getLogLevelError() {
		return logLevelError;
	}

	/**
	 * @return WARNログレベル
	 */
	public Level getLogLevelWarn() {
		return logLevelWarn;
	}

	/**
	 * @return INFOログレベル
	 */
	public Level getLogLevelInfo() {
		return logLevelInfo;
	}

	/**
	 * @return DEBUGログレベル
	 */
	public Level getLogLevelDebug() {
		return logLevelDebug;
	}

	/**
	 * パラメータをパラメータマーカー[?]に変換したSQLを返す.
	 *
	 * @return sql文字列
	 */
	public String getPreparedSql() {
		return sql;
	}

	/**
	 * SQLのソースイメージ(コンストラクタで渡した物)を返す.
	 *
	 * @return sql文字列
	 */
	public String getSourceSql() {
		return srcSql;
	}

	/**
	 * 現在のログ出力状況を取得.
	 * <p>
	 *
	 * @return true:ログを出力　false:ログは出力しません
	 */
	public int getSqlLogMode() {
		return sqlLogMode;
	}

	/**
	 * パラメータの名前と値をログ出力する.
	 * @param logLevel  ログレベル
	 */
//	public void logParameter(int logLevel) {
	public void logParameter(Level logLevel) {
		StringBuffer logMessage;
		Iterator<Map.Entry<String, String>> ite = paramStrMap.entrySet().iterator();
		while (ite.hasNext()) {
			Entry<String, String> entry = ite.next();
			logMessage = new StringBuffer();
			logMessage.append("{").append(entry.getKey()).append("} = [").append(entry.getValue()).append("]");
			logger.log(logLevel, logMessage.toString());
		}
	}

	/**
	 * パラメータの名前と値、および、パラメータマーカーとの対応をログ出力する.
	 * @param logLevel  ログレベル
	 */
//	public void logParameterExpanded(int logLevel) {
	public void logParameterExpanded(Level logLevel) {
		StringBuffer logMessage;
		Iterator<Map.Entry<String, String>> ite = paramStrMap.entrySet().iterator();
		while (ite.hasNext()) {
			Entry<String, String> entry = ite.next();
			logMessage = new StringBuffer();
			logMessage.append("{").append(entry.getKey()).append("} = [").append(entry.getValue()).append("]");
			logger.log(logLevel, logMessage.toString());
			logMessage = new StringBuffer("    for");
			Iterator<Integer> ite2 = paramMarkerMap.get(entry.getKey()).iterator();
			while (ite2.hasNext()) {
				logMessage.append(" ");
				logMessage.append(ite2.next());
			}
			logger.log(logLevel, logMessage.toString());
		}
	}

	/**
	 * SQLをログ出力.
	 * <p>
	 * SQLの？をパラメータで変換しログへ出力する。ただしログ出力状態の時のみ
	 *
	 * @param exeTime
	 *            実行時間
	 */
	private void logSql(long exeTime) {
//		int logLevel = getSqlLogLevel(exeTime);
		Level logLevel = getSqlLogLevel(exeTime);
//		if (logLevel != Logger.LV_OFF) {
		if (logLevel != Level.OFF) {
			StringBuffer logMessage;
			switch (sqlLogMode) {
			case SQL_LOG_MODE_EXECUTABLE:
				logMessage = new StringBuffer();
				logMessage.append("(").append(exeTime).append("ms)");
				logMessage.append(logTitle);
				logMessage.append(getExecutableSql());
				logger.log(logLevel, logMessage.toString());
				break;
			case SQL_LOG_MODE_SOURCE:
				logMessage = new StringBuffer();
				logMessage.append("(").append(exeTime).append("ms)");
				logMessage.append(logTitle);
				logMessage.append(srcSql.replaceAll(LINE_SEPARATOR, " "));
				logger.log(logLevel, logMessage.toString());
				logParameter(logLevel);
				break;
			case SQL_LOG_MODE_PREPARED:
				logMessage = new StringBuffer();
				logMessage.append("(").append(exeTime).append("ms)");
				logMessage.append(logTitle);
				logMessage.append(sql.replaceAll(LINE_SEPARATOR, " "));
				logger.log(logLevel, logMessage.toString());
				logParameterExpanded(logLevel);
				break;
			default:
			}
		}
	}

	/**
	 * SQLログ出力レベルを取得.
	 * <p>
	 * @param exeTime
	 *            実行時間
	 * @return ログレベル
	 */
//	private int getSqlLogLevel(long exeTime) {
//		return exeTime < ResourceUtility.getResourceInt("sql.response.heavy", Integer.MAX_VALUE)
//			  ? ResourceUtility.getResourceInt("sql.loglv.default", logLevelDebug)
//			  : ResourceUtility.getResourceInt("sql.loglv.heavy", logLevelDebug);
//	}
	private Level getSqlLogLevel(long exeTime) {
		return Level.ALL;
	}
	/**
	 * SQLの実行（照会＆格納）.
	 * <p>
	 * SQLを実行して、結果をMapのArrayListに格納します。
	 *
	 * @param convertNullToNullString
	 *            nullを空文字列に変換する場合 true を指定します
	 * @return 照会結果<br>
	 *         列名(大文字)をkeyとして、値をvalueとするMapが格納されたArrayList
	 * @throws SQLException
	 *             SQL例外
	 */
	public ArrayList<HashMap<String, Object>> selectToMapArray(boolean convertNullToNullString) throws SQLException {

		// 戻り値用 ArrayList
		ArrayList<HashMap<String, Object>> data = new ArrayList<HashMap<String, Object>>();

		// SQL実行
		ResultSet rs = executeQuery(); // 内部関数で例外処理済みなのでcatchせずthrow
		try {
			// 列数取得
			ResultSetMetaData rsmd = rs.getMetaData();
			int col = rsmd.getColumnCount();
			// 列名取得
			String[] name = new String[col];
			for (int c = 0; c < col; ++c) {
				name[c] = rsmd.getColumnName(c + 1).toUpperCase();
			}
			while (rs.next()) {
				// 戻り値用 HashMap
				// １HashMapは１レコードに対応
				HashMap<String, Object> hm = new HashMap<String, Object>();
				// SELECT結果をHashMapに格納
				for (int c = 0; c < col; ++c) {
					Object obj = rs.getObject(name[c]);
					if (!convertNullToNullString || obj != null) {
						hm.put(name[c], obj);
					} else {
						hm.put(name[c], "");
					}
				}
				// HashMapをArrayListに格納
				data.add(hm);
			}
			return data;
		} catch (SQLException e) {
			logger.log(logLevelWarn, "(SQL例外)"+logTitle+" [ErrorCode:" + e.getErrorCode()
								   + "] [SQLState:" + e.getSQLState() + "] [Message:" + e.getMessage() + "]");
			throw e;
		} finally {
			if (rs != null) {
				rs.close();
			}
		}
	}

	/**
	 * BigDecimalパラメータ設定.
	 * <p>
	 *
	 * @param paramName
	 *            パラメータ名
	 * @param value
	 *            パラメータ値
	 * @throws SQLException
	 *             SQL例外
	 */
	public void setBigDecimalAtName(String paramName, BigDecimal value) throws SQLException {
		Set<Integer> markers = paramMarkerMap.get(paramName);
		if (markers == null) {
			return; // SQLに無いパラメータは無視
		}
		for (Iterator<Integer> ite = markers.iterator(); ite.hasNext();) {
			pstmt.setBigDecimal(((Integer) ite.next()).intValue(), value);
		}
		paramStrMap.put(paramName, value.toString());
	}

	/**
	 * intパラメータ設定.
	 * <p>
	 *
	 * @param paramName
	 *            パラメータ名
	 * @param value
	 *            パラメータ値
	 * @throws SQLException
	 *             SQL例外
	 */
	public void setIntAtName(String paramName, int value) throws SQLException {
		Set<Integer> markers = paramMarkerMap.get(paramName);
		if (markers == null) {
			return; // SQLに無いパラメータは無視
		}
		for (Iterator<Integer> ite = markers.iterator(); ite.hasNext();) {
			pstmt.setInt(((Integer) ite.next()).intValue(), value);
		}
		paramStrMap.put(paramName, String.valueOf(value));
	}

	/**
	 * ログレベルの変更.
	 * <p>
	 * ログ出力の優先度を調整したい場合に使用します。<br>
	 * 例外はリスローされるので、catch先でのログ出力に任せる場合などは、OFFにすることもできます。
	 * @param logLevelError
	 *            ERRORログレベル(Logger.LV_*)
	 */
	public void setLogLevelError(Level logLevelError) {
		this.logLevelError = logLevelError;
	}

	/**
	 * ログレベルの変更.
	 * <p>
	 * ログ出力の優先度を調整したい場合に使用します。<br>
	 * 例外はリスローされるので、catch先でのログ出力に任せる場合などは、OFFにすることもできます。
	 * @param logLevelWarn
	 *            WARNログレベル(Logger.LV_*)
	 */
	public void setLogLevelWarn(Level logLevelWarn) {
		this.logLevelWarn = logLevelWarn;
	}

	/**
	 * ログレベルの変更.
	 * <p>
	 * ログ出力の優先度を調整したい場合に使用します。<br>
	 * 例外はリスローされるので、catch先でのログ出力に任せる場合などは、OFFにすることもできます。
	 * @param logLevelInfo
	 *            INFOログレベル(Logger.LV_*)
	 */
	public void setLogLevelInfo(Level logLevelInfo) {
		this.logLevelInfo = logLevelInfo;
	}

	/**
	 * ログレベルの変更.
	 * <p>
	 * ログ出力の優先度を調整したい場合に使用します。<br>
	 * デバッグログレベルを引き上げることにより、デバッグログが抑止されている場合でも、
	 * 指定インスタンスの実行SQLをログに出力することができます。
	 * @param logLevelDebug
	 *            DEBUGログレベル(Logger.LV_*)
	 */
	public void setLogLevelDebug(Level logLevelDebug) {
		this.logLevelDebug = logLevelDebug;
	}

	/**
	 * ログタイトル設定.
	 * <p>
	 * ログ出力時の行タイトルを設定する
	 *
	 * @param logTitle
	 *            ログタイトル
	 */
	public void setLogTitle(String logTitle) {
		this.logTitle = "<<" + logTitle + ">>";
	}

	/**
	 * longパラメータ設定.
	 * <p>
	 *
	 * @param paramName
	 *            パラメータ名
	 * @param value
	 *            パラメータ値
	 * @throws SQLException
	 *             SQL例外
	 */
	public void setLongAtName(String paramName, long value) throws SQLException {
		Set<Integer> markers = paramMarkerMap.get(paramName);
		if (markers == null) {
			return; // SQLに無いパラメータは無視
		}
		for (Iterator<Integer> ite = markers.iterator(); ite.hasNext();) {
			pstmt.setLong(((Integer) ite.next()).intValue(), value);
		}
		paramStrMap.put(paramName, String.valueOf(value));
	}

	/**
	 * Objectパラメータ設定.
	 * <p>
	 *
	 * @param paramName
	 *            パラメータ名
	 * @param value
	 *            パラメータ値
	 * @throws SQLException
	 *             SQL例外
	 */
	public void setObjectAtName(String paramName, Object value) throws SQLException {
		Set<Integer> markers = paramMarkerMap.get(paramName);
		if (markers == null) {
			return; // SQLに無いパラメータは無視
		}
		for (Iterator<Integer> ite = markers.iterator(); ite.hasNext();) {
			pstmt.setObject(((Integer) ite.next()).intValue(), value);
		}
		if (value != null) {
			paramStrMap.put(paramName, "'" + value.toString().replaceAll("'", "''") + "'");
		} else {
			paramStrMap.put(paramName, null);
		}
	}

	/**
	 * Objectパラメータ設定.
	 * <p>
	 *
	 * @param paramName
	 *            パラメータ名
	 * @param value
	 *            パラメータ値
	 * @param targetSqlType
	 *            データベースに送られる (java.sql.Types で定義される) SQL 型
	 * @throws SQLException
	 *             SQL例外
	 */
	public void setObjectAtName(String paramName, Object value, int targetSqlType) throws SQLException {
		Set<Integer> markers = paramMarkerMap.get(paramName);
		if (markers == null) {
			return; // SQLに無いパラメータは無視
		}
		for (Iterator<Integer> ite = markers.iterator(); ite.hasNext();) {
			pstmt.setObject(((Integer) ite.next()).intValue(), value, targetSqlType);
		}
		if (value != null) {
			paramStrMap.put(paramName, "'" + value.toString().replaceAll("'", "''") + "'");
		} else {
			paramStrMap.put(paramName, null);
		}
	}

	/**
	 * Objectパラメータ設定.
	 * <p>
	 *
	 * @param paramName
	 *            パラメータ名
	 * @param value
	 *            パラメータ値
	 * @param targetSqlType
	 *            データベースに送られる (java.sql.Types で定義される) SQL 型
	 * @param scale
	 *            java.sql.Types.DECIMAL や java.sql.Types.NUMERIC型では、
	 *            小数点以下の桁数。ほかのすべての型では、この値は無視される
	 * @throws SQLException
	 *             SQL例外
	 */
	public void setObjectAtName(String paramName, Object value, int targetSqlType, int scale) throws SQLException {
		Set<Integer> markers = paramMarkerMap.get(paramName);
		if (markers == null) {
			return; // SQLに無いパラメータは無視
		}
		for (Iterator<Integer> ite = markers.iterator(); ite.hasNext();) {
			pstmt.setObject(((Integer) ite.next()).intValue(), value, targetSqlType, scale);
		}
		if (value != null) {
			paramStrMap.put(paramName, "'" + value.toString().replaceAll("'", "''") + "'");
		} else {
			paramStrMap.put(paramName, null);
		}
	}

	/**
	 * ログを出力するかどうか設定.
	 * <p>
	 *
	 * @param sqlLogMode
	 *            true:ログを出力　false:ログは出力しません
	 */
	public void setSqlLogMode(int sqlLogMode) {
		this.sqlLogMode = sqlLogMode;
	}

	/**
	 * Stringパラメータ設定.
	 * <p>
	 *
	 * @param paramName
	 *            パラメータ名
	 * @param value
	 *            パラメータ値
	 * @throws SQLException
	 *             SQL例外
	 */
	public void setStringAtName(String paramName, String value) throws SQLException {
		Set<Integer> markers = paramMarkerMap.get(paramName);
		if (markers == null) {
			return; // SQLに無いパラメータは無視
		}
		for (Iterator<Integer> ite = markers.iterator(); ite.hasNext();) {
			pstmt.setString(((Integer) ite.next()).intValue(), value);
		}
		paramStrMap.put(paramName, "'" + value.replaceAll("'", "''") + "'");
	}

}
